package org.geoscrape;

import static org.geoscrape.util.HtmlParser.getAllInstances;
import static org.geoscrape.util.HtmlParser.getContent;
import static org.geoscrape.util.HtmlParser.getTagContent;

import java.io.IOException;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;

import org.geoscrape.util.HtmlParser;
import org.geoscrape.util.Tools;
import org.geoscrape.util.WebClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

/**
 *
 */
public class Cache extends BasePlace implements Serializable
{
	private static final long serialVersionUID = 4163848032478184372L;
	private static final String CACHE_SEARCH_BASE = "http://www.geocaching.com/seek/cache_details.aspx?wp=";
	private static final Object BLANK_ATTRIBUTE = "/images/attributes/attribute-blank.gif";

	private String cacheCode;
	private Long id;
	private String guid;
	private long placedAt;
	private CacheType cacheType;
	private CacheSize cacheSize;
	private double difficultyRating;
	private double terrainRating;
	private int favourited;
	private String shortDescription;
	private String longDescription;
	private String hint;
	private String locationDescription;
	private String shortCutURL;

	private DateFormat mapParseDateFormat = new SimpleDateFormat("d/M/yyyy");

	private List<CacheLog> logs = new ArrayList<CacheLog>();
	private Set<Attribute> attributes = new HashSet<Attribute>();
	private List<WayPoint> waypoints = new ArrayList<WayPoint>();
	private Boolean found;
	private boolean foundByUser;
	private boolean disabled;
	private boolean archived;
	private boolean premiumonly;
	private String userToken;
	private int pageCount;
	private String mapRequestCode;
	private int estimatelevel;
	private int trackableCount;
	private Long lastFoundDate;
	private boolean unavailabletous;

	public Cache()
	{
		// default constructor
	}

	/**
	 * @param cacheCode
	 */
	public Cache(String cacheCode)
	{
		this.cacheCode = cacheCode;
	}

	/**
	 * Populate the cache details.
	 * 
	 * @param login
	 * @param fullLogs
	 *            true if full logs, otherwise only recent logs will be loaded.
	 * @throws IOException
	 * @throws ParseException
	 */
	public void populate(Login login, boolean fullLogs) throws IOException, ParseException
	{
		if (cacheCode == null)
		{
			if (this.mapRequestCode != null)
			{
				try
				{
					this.populateFromMap(login);
				}
				catch (JSONException e)
				{
					throw new IOException(e);
				}
			}
			else
			{
				throw new IllegalStateException("cacheCode must be set before calling populate.");
			}
		}
		// get the location of the cache description
		String urlString = CACHE_SEARCH_BASE + getCacheCode();
		if (shortCutURL != null)
		{
			urlString = shortCutURL;
		}
		WebClient client = login.getClient();
		client.setRequestMethod("GET");
		client.goTo(urlString);
		// load up the cache HTML
		String contents = client.getContentsAsString();

		userToken = getContent("userToken = '", "';", contents);
		// parse the HTML
		parse(contents, login);
		if (fullLogs)
		{
			while (retrieveMoreLogs(login))
			{
			}
		}
	}

	/**
	 * Calls the GC.com site to get more logs. Returns true if more logs are
	 * available, false otherwise.
	 * 
	 * @return false if all logs are loaded.
	 * @throws IOException
	 * @throws ParseException
	 */
	public boolean retrieveMoreLogs(Login login) throws IOException, ParseException
	{
		boolean res = false;
		if (userToken != null)
		{
			pageCount = logs.size() / 25;
			WebClient client = login.getClient();
			client.setRequestMethod("GET");
			String request = "http://www.geocaching.com/seek/geocache.logbook?tkn=";
			request += userToken;
			request += "&idx=" + Integer.toString(1 + pageCount);
			request += "&num=25&decrypt=true";
			client.goTo(request);
			String contents = client.getContentsAsString();
			try
			{
				res = parseJsonLogs(contents, login);
			}
			catch (JSONException e)
			{
				System.out.println("Problem with logs for cache " + this.getCacheCode());
				throw new IOException(e);
			}
		}

		return res;
	}

	/**
	 * @param contents
	 * @throws ParseException
	 * @throws JSONException
	 */
	private boolean parseJsonLogs(String contents, Login login) throws ParseException, JSONException
	{
		boolean res = false;
		// de-tokenise the string
		JSONTokener jtok = new JSONTokener(contents);
		while (jtok.more())
		{
			Object o = jtok.nextValue();

			if (o instanceof JSONObject)
			{
				JSONObject obj = (JSONObject) o;
				String status = (String) obj.get("status");
				if (status.equals("success"))
				{
					JSONArray data = obj.getJSONArray("data");
					for (int index = 0; index < data.length(); index++)
					{
						JSONObject tmp = data.getJSONObject(index);
						CacheLog log = new CacheLog(tmp, login);
						this.getLogs().add(log);
						if (getId() == null)
						{
							this.setId(tmp.getLong("CacheID"));
						}
					}
					JSONObject pageinfo = obj.getJSONObject("pageInfo");
					if (pageinfo != null)
					{
						if (pageinfo.has("totalRows"))
						{
							int totalLogs = pageinfo.getInt("totalRows");
							if (totalLogs > logs.size())
							{
								// there are still more pages to return
								res = true;
							}
						}
					}
				}
			}
			else
			{
				System.out.println("Non-JSON Object:" + o);
			}
		}
		return res;
	}

	private void parse(String contents, Login login) throws ParseException
	{
		String rawContents = contents;
		contents = HtmlParser.convertHtmlChar(contents);
		populateOwnerAndHiddenDate(contents, login);
		String sizeContents = getContent("<img src=\"/images/icons/container/", ".gif", contents);
		if (sizeContents != null)
		{
			setCacheSize(CacheSize.valueOf(sizeContents.toUpperCase()));

			String typeString = getContent("<img src=\"/images/WptTypes/", ".gif", contents);
			if (typeString == null)
			{
				// some pages have this in lowercase,with full domain
				typeString = getContent("/images/wpttypes/", ".gif", contents);
			}

			setCacheType(typeString);
			populateDT(contents);

			if (!this.isUnavailableToUs())
			{
				// get name
				setName(getContent("<span id=\"ctl00_ContentBody_CacheName\">", "</span>", contents));
				String favouriteString = getContent("<span class=\"favorite-value\">", "</span>", contents);
				if (favouriteString != null)
				{
					setFavourited(Integer.parseInt(favouriteString.trim()));
				}
				int shortStart = contents.indexOf("<span id=\"ctl00_ContentBody_ShortDescription\">");
				if (shortStart > 0)
				{
					setShortDescription(getTagContent("span", contents.substring(shortStart - 1)));
				}
				int longStart = contents.indexOf("<span id=\"ctl00_ContentBody_LongDescription\">");
				if (longStart > 0)
				{
					setLongDescription(getTagContent("span", contents.substring(longStart - 1)));
				}
				String encodedHint = getContent("<div id=\"div_hint\" class=\"span-8 WrapFix\">", "</div>", contents);
				if (encodedHint != null && encodedHint.trim().length() > 0)
				{
					String decodedHint = Tools.rot13(encodedHint);
					setHint(decodedHint);
				}
				populateId(contents);
				populateGuid(contents);
				populateLocation(contents);
				populateWaypoints(contents);
				populateTrackables(contents);
				populateAttributes(contents);
				try
				{
					populateInitialLogs(rawContents, login);
				}
				catch (IOException e)
				{
					e.printStackTrace();
				}
			}
			else
			{
				String nameContent = getContent("id=\"ctl00_ContentBody_uxWptTypeImage\"", "</h2>", contents);
				String name = getContent("/>", "(", nameContent);
				if (name != null)
				{
					setName(name.trim());
				}
			}

			// check if it is found
			if (contents.contains("ctl00_ContentBody_hlFoundItLog"))
			{
				this.setFoundByUser(true);
			}
			else
			{
				this.setFoundByUser(false);
			}

			// check if it has been archived
			if (contents.contains("This cache has been archived"))
			{
				this.setArchived(true);
			}
			else
			{
				this.setArchived(false);
			}
		}
		else
		{
			throw new ParseException("Could not get cache size.",0);
		}
	}

	/**
	 * @param contents
	 * @throws ParseException
	 * @throws IOException
	 */
	private void populateInitialLogs(String contents, Login login) throws IOException, ParseException
	{
		String initialLogs = getContent("initalLogs = {", "};", contents);
		if (initialLogs != null)
		{
			try
			{

				parseJsonLogs("{" + initialLogs + "}", login);
			}
			catch (JSONException e)
			{
				System.out.println("Problem with initial logs for cache " + this.getCacheCode());
				throw new IOException(e);
			}
		}
	}

	private void populateGuid(String contents)
	{
		setGuid(getContent("/bookmarks/mark.aspx?guid=", "&", contents));

	}

	/**
	 * Get the cache ID.
	 * 
	 * @param contents
	 */
	private void populateId(String contents)
	{
		String idString = getContent("<a href=\"/seek/log.aspx?ID=", "&lcn=", contents);
		if (idString != null)
		{
			setId(Long.parseLong(idString));
		}

	}

	public void setFound(Boolean found)
	{
		this.found = found;
	}

	public Boolean isFound()
	{
		return this.found;
	}

	public void setShortCutURL(String url)
	{
		this.shortCutURL = url;
	}

	public void setFoundByUser(boolean found)
	{
		this.foundByUser = found;
	}

	public boolean isFoundByUser()
	{
		return this.foundByUser;
	}

	private void populateLocation(String contents)
	{
		// indicate that the location is accurate and not estimated
		this.setEstimateLevel(-1);

		String locationContent = getContent("id=\"uxLatLon\"", "</", contents).trim();
		int beginIndex = locationContent.indexOf(">");
		if (beginIndex >= 0)
		{
			locationContent = locationContent.substring(beginIndex + 1);
		}
		setLocation(new Location(locationContent));
	}

	private void populateAttributes(String contents)
	{
		String startMarker = "<div class=\"WidgetBody\">";
		String attrMarker = "<img src=\"";
		String attributeString = getContent("<div id=\"ctl00_ContentBody_detailWidget\"", "</div>", contents);
		int index = attributeString.indexOf(startMarker);
		if (index >= 0)
		{
			attributeString = attributeString.substring(index + startMarker.length());
			index = attributeString.indexOf(attrMarker);
			while (index >= 0)
			{
				int startIndex = index + attrMarker.length();
				int endIndex = attributeString.indexOf("\"", startIndex);
				String attribute = attributeString.substring(startIndex, endIndex);
				if (attribute != null && !attribute.equals(BLANK_ATTRIBUTE))
				{
					Attribute attr = Attribute.parse(attribute);
					if (attr != null)
					{
						this.attributes.add(attr);
						if (attr.getId() < 0)
						{
							System.out.println("Don't know value for " + attr);
						}
					}
				}
				index = attributeString.indexOf(attrMarker, startIndex);
			}
		}
	}

	/**
	 * Parse the trackables, if there are any.
	 * 
	 * @param contents
	 */
	private void populateTrackables(String contents)
	{
		// TODO Parse trackables list

	}

	/**
	 * Parse the waypoints, if there are any.
	 * 
	 * @param contents
	 */
	private void populateWaypoints(String contents)
	{
		String waypointTable = getContent("<table class=\"Table\" id=\"ctl00_ContentBody_Waypoints\">", "</table>",
				contents);
		if (waypointTable != null)
		{
			String tableBody = getContent("<tbody>", "</tbody>", waypointTable);
			List<String> tableRows = getAllInstances("<tr", "</tr>", tableBody);
			for (String tableRow : tableRows)
			{
				List<String> tableEntries = getAllInstances("<td>", "</td>", tableRow);
				if (tableEntries.size() >= 7)
				{
					WayPoint wp = new WayPoint();
					wp.setPartOf(this);
					// get type
					String type = getContent("title=\"", "\"", tableEntries.get(1));
					wp.setType(type);
					// get prefix
					String prefix = getContent("\">", "<", tableEntries.get(2)).trim();
					wp.setPrefix(prefix);
					// get the location
					String location = tableEntries.get(5).trim();
					location = location.replaceAll("&nbsp;", "");
					try
					{
						wp.setLocation(new Location(location));
					}
					catch (Exception e)
					{
						// do nothing
					}

					this.waypoints.add(wp);
				}
			}
		}
	}

	/**
	 * Populate the Difficulty/Terrain rating.
	 * 
	 * @param contents
	 */
	private void populateDT(String contents)
	{
		String trimmedContents = getContent("<meta name=\"og:description\"", null, contents);
		if (trimmedContents != null)
		{
			String description = getContent("<meta name=\"description\" content=\"", "/>", trimmedContents);
			if (description != null)
			{
				String difficulty = getContent("difficulty of", ",", description).trim();
				setDifficultyRating(Double.parseDouble(difficulty));
				String terrain = getContent("terrain of", ". ", description).trim();
				setTerrainRating(Double.parseDouble(terrain));
				setLocationDescription(getContent("located in ", ".", description).trim());
			}
			else
			{
				// member-only cache, and we're not logged in as member
				description = getContent("<p class=\"PMCacheInfoSpacing\">", "</p>", trimmedContents);
				String diffString = getContent("ctl00_ContentBody_lblDifficulty\">", "<strong>", description);
				diffString = getContent("alt=\"", " out", diffString);
				if (diffString != null)
				{
					setDifficultyRating(Double.parseDouble(diffString));
				}
				String terrainString = getContent("ctl00_ContentBody_lblTerrain", "/>", description);
				terrainString = getContent("alt=\"", " out", terrainString);
				if (terrainString != null)
				{
					setTerrainRating(Double.parseDouble(terrainString));
				}
			}
		}
	}

	private void populateOwnerAndHiddenDate(String contents, Login login) throws ParseException
	{
		// find the start of the name/owner/date description
		int startIndex = contents.indexOf("<span id=\"ctl00_ContentBody_CacheName\">");
		// find the start index of the owner description
		int hiderStart = contents.indexOf("<div class=\"minorCacheDetails Clear\">", startIndex);
		if (hiderStart >= 0)
		{
			// find the hider link
			String hiderData = contents.substring(hiderStart);
			String hiderUrl = getContent("<a href=\"", "\">", hiderData);
			// find the owner name
			String hiderName = getContent(hiderUrl + "\">", "</a>", hiderData);
			if (hiderUrl.contains("&"))
			{
				// strip all unused parts of the hider url
				hiderUrl = hiderUrl.substring(0, hiderUrl.indexOf("&"));
			}
			// create hider object
			this.setHider(new Cacher(hiderName, hiderUrl));
			// find the start index of the hidden date
			int dateStart = contents.indexOf("<div id=\"ctl00_ContentBody_mcd2\">", hiderStart + 1);
			dateStart = contents.indexOf(":", dateStart) + 1;
			int dateEnd = contents.indexOf("<", dateStart);
			// parse the hidden date
			String dateString = contents.substring(dateStart, dateEnd).trim();
			SimpleDateFormat sdf = new SimpleDateFormat(login.getDateFormat());
			sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
			Date d = sdf.parse(dateString);
			this.setPlacedAt(d.getTime());

			// set the owner
			String ownerName = getContent("href=\"/seek/nearest.aspx?u=", "\">", contents);
			if (ownerName.equals(hiderName))
			{
				// owner and hider are the same
				setOwner(getHider());
			}
			else
			{
				setOwner(new Cacher(ownerName));
			}

		}
		else
		{
			this.setUnavailableToUs(true);
			this.setPremiumonly(true);
			// member-only cache, and we're not logged in as premium member
			String ownerName = getContent("<span id=\"ctl00_ContentBody_uxCacheType\">A cache by ", "</span>", contents);
			this.setHider(new Cacher(ownerName));
			setOwner(getHider());
		}
	}

	/**
	 * @param b
	 */
	public void setUnavailableToUs(boolean b)
	{
		this.unavailabletous = b;
	}

	public boolean isUnavailableToUs()
	{
		return this.unavailabletous;
	}

	/**
	 * Use the map request code to populate a limited subset of the cache data.
	 * This is the same info that is displayed when a cache is clicked in the
	 * website map.
	 * 
	 * If the map request code is not set this method will fail.
	 * 
	 * @throws IOException
	 * @throws JSONException
	 * @throws ParseException
	 */
	public void populateFromMap(Login login) throws IOException, JSONException, ParseException
	{
		WebClient client = login.getClient();
		if (this.mapRequestCode == null)
		{
			throw new IllegalStateException("Map request code not set.");
		}
		client.setRequestMethod("GET");
		StringBuilder request = new StringBuilder("http://www.geocaching.com/map/map.details?i=");
		request.append(this.mapRequestCode);
		request.append("&_=");
		request.append(Long.toString(System.currentTimeMillis()));
		client.goTo(request.toString());
		String contents = client.getContentsAsString();
		if (contents != null)
		{
			contents = contents.trim();
			// de-tokenise the string
			JSONTokener jtok = new JSONTokener(contents);
			if (jtok.more())
			{
				JSONObject o = (JSONObject) jtok.nextValue();

				String status = o.getString("status");
				if (!status.equals("success"))
				{
					System.out.println("Warning: status was \"" + status + "\"");
				}
				JSONArray array = o.getJSONArray("data");
				JSONObject obj = array.getJSONObject(0);
				this.setCacheCode(obj.getString("gc"));
				this.setGuid(obj.getString("g"));
				this.setDisabled(!obj.getBoolean("available"));
				this.setArchived(obj.getBoolean("archived"));
				this.setPremiumonly(obj.getBoolean("subrOnly"));
				this.setFavourited(obj.getInt("fp"));
				JSONObject difficulty = obj.getJSONObject("difficulty");
				this.setDifficultyRating(difficulty.getDouble("text"));
				JSONObject terrain = obj.getJSONObject("terrain");
				this.setTerrainRating(terrain.getDouble("text"));
				String hiddenDateString = obj.getString("hidden");
				Date hiddenDate = mapParseDateFormat.parse(hiddenDateString);
				this.setPlacedAt(hiddenDate.getTime());
				JSONObject container = obj.getJSONObject("container");
				String containerType = container.getString("text");
				this.setCacheSize(CacheSize.parse(containerType));
				JSONObject type = obj.getJSONObject("type");
				int typeValue = type.getInt("value");
				this.setCacheType(typeValue);
				JSONObject owner = obj.getJSONObject("owner");
				Cacher cowner = new Cacher(owner.getString("text"));
				cowner.setProfilePage("http://www.geocaching.com/profile/?guid=" + owner.getString("value"));
				this.setOwner(cowner);
				this.setHider(cowner);
			}
		}
	}

	/**
	 * @return the cacheCode
	 */
	public String getCacheCode()
	{
		return cacheCode;
	}

	/**
	 * @param cacheCode
	 *            the cacheCode to set
	 */
	public void setCacheCode(String cacheCode)
	{
		this.cacheCode = cacheCode;
	}

	/**
	 * @return the placedAt
	 */
	public long getPlacedAt()
	{
		return placedAt;
	}

	/**
	 * @param placedAt
	 *            the placedAt to set
	 */
	public void setPlacedAt(long placedAt)
	{
		this.placedAt = placedAt;
	}

	/**
	 * @return the cacheType
	 */
	public CacheType getCacheType()
	{
		return cacheType;
	}

	/**
	 * @param cacheType
	 *            the cacheType to set
	 */
	public void setCacheType(CacheType cacheType)
	{
		this.cacheType = cacheType;
	}

	/**
	 * Set the cache type based on the cache image index.
	 * 
	 * @param cacheImageIndex
	 */
	public void setCacheType(int cacheImageIndex)
	{
		if (cacheImageIndex == 3653)
		{
			setCacheType(CacheType.LOST_AND_FOUND_EVENT);
		}
		else if (cacheImageIndex == 1858)
		{
			setCacheType(CacheType.WHERIGO);
		}
		else if (cacheImageIndex == 137)
		{
			setCacheType(CacheType.EARTH_CACHE);
		}
		else if (cacheImageIndex == 453)
		{
			setCacheType(CacheType.MEGA_EVENT);
		}
		else if (cacheImageIndex == 1304)
		{
			setCacheType(CacheType.GPSADVENTURE);
		}
		else if(cacheImageIndex == 3773)
		{
			setCacheType(CacheType.GCHQ);
		}
		else if (cacheImageIndex == 4738)
		{
			setCacheType(CacheType.EVENT);
		}
		else
		{
			setCacheType(CacheType.values()[cacheImageIndex - 2]);
		}
	}

	public void setCacheType(String cacheType)
	{
		if (cacheType.equals("earthcache"))
		{
			setCacheType(CacheType.EARTH_CACHE);
		}
		else
		{
			setCacheType(Integer.parseInt(cacheType));
		}
	}

	/**
	 * @return the cacheSize
	 */
	public CacheSize getCacheSize()
	{
		return cacheSize;
	}

	/**
	 * @param cacheSize
	 *            the cacheSize to set
	 */
	public void setCacheSize(CacheSize cacheSize)
	{
		this.cacheSize = cacheSize;
	}

	/**
	 * @return the difficultyRating
	 */
	public double getDifficultyRating()
	{
		return difficultyRating;
	}

	/**
	 * @param difficultyRating
	 *            the difficultyRating to set
	 */
	public void setDifficultyRating(double difficultyRating)
	{
		this.difficultyRating = difficultyRating;
	}

	/**
	 * @return the terrainRating
	 */
	public double getTerrainRating()
	{
		return terrainRating;
	}

	/**
	 * @param terrainRating
	 *            the terrainRating to set
	 */
	public void setTerrainRating(double terrainRating)
	{
		this.terrainRating = terrainRating;
	}

	/**
	 * @return the favourited
	 */
	public int getFavourited()
	{
		return favourited;
	}

	/**
	 * @param favourited
	 *            the favourited to set
	 */
	public void setFavourited(int favourited)
	{
		this.favourited = favourited;
	}

	/**
	 * @return the shortDescription
	 */
	public String getShortDescription()
	{
		return shortDescription;
	}

	/**
	 * @param shortDescription
	 *            the shortDescription to set
	 */
	public void setShortDescription(String shortDescription)
	{
		this.shortDescription = shortDescription;
	}

	/**
	 * @return the longDescription
	 */
	public String getLongDescription()
	{
		return longDescription;
	}

	/**
	 * @param longDescription
	 *            the longDescription to set
	 */
	public void setLongDescription(String longDescription)
	{
		this.longDescription = longDescription;
	}

	/**
	 * @return the hint
	 */
	public String getHint()
	{
		return hint;
	}

	/**
	 * @param hint
	 *            the hint to set
	 */
	public void setHint(String hint)
	{
		this.hint = hint;
	}

	public boolean isDisabled()
	{
		return disabled;
	}

	public void setDisabled(boolean b)
	{
		this.disabled = b;
	}

	/**
	 * @return the locationDescription
	 */
	public String getLocationDescription()
	{
		return locationDescription;
	}

	/**
	 * @param locationDescription
	 *            the locationDescription to set
	 */
	public void setLocationDescription(String locationDescription)
	{
		this.locationDescription = locationDescription;
	}

	/**
	 * @return the logs
	 */
	public List<CacheLog> getLogs()
	{
		return logs;
	}

	/**
	 * Return the attributes
	 */
	public Set<Attribute> getAttributes()
	{
		return attributes;
	}

	/**
	 * @return the waypoints
	 */
	public List<WayPoint> getWaypoints()
	{
		return waypoints;
	}

	/**
	 * 
	 * @param pixelX
	 * @param pixelY
	 * @param baseX
	 * @param baseY
	 * @param sizeX
	 * @param sizeY
	 */
	public void estimateLocation(int pixelX, int pixelY, int baseX, int baseY, int sizeX, int sizeY)
	{
	}

	/**
	 * @return the id
	 */
	public Long getId()
	{
		return id;
	}

	/**
	 * @param id
	 *            the id to set
	 */
	public void setId(Long id)
	{
		this.id = id;
	}

	/**
	 * @return the guid
	 */
	public String getGuid()
	{
		return guid;
	}

	/**
	 * @param guid
	 *            the guid to set
	 */
	public void setGuid(String guid)
	{
		this.guid = guid;
	}

	public boolean isArchived()
	{
		return archived;
	}

	public void setArchived(boolean archived)
	{
		this.archived = archived;
	}

	/**
	 * @return the premiumonly
	 */
	public boolean isPremiumonly()
	{
		return premiumonly;
	}

	/**
	 * @param premiumonly
	 *            the premiumonly to set
	 */
	public void setPremiumonly(boolean premiumonly)
	{
		this.premiumonly = premiumonly;
	}

	/**
	 * Set the string used to request short info via the map interface.
	 * 
	 * @param magicString
	 */
	public void setMapRequestCode(String magicString)
	{
		this.mapRequestCode = magicString;
	}

	/**
	 * Set the zoom level at which the location was estimated.
	 * 
	 * @param zoom
	 */
	public void setEstimateLevel(int zoom)
	{
		this.estimatelevel = zoom;
	}

	/**
	 * Get the zoom level at which the location was estimated. If the zoom level
	 * is negative the location is accurate.
	 * 
	 * @return
	 */
	public int getEstimateLevel()
	{
		return this.estimatelevel;
	}

	/**
	 * @param trackables
	 */
	public void setTrackableCount(int trackables)
	{
		this.trackableCount = trackables;
	}

	public int getTrackableCount()
	{
		return this.trackableCount;
	}

	/**
	 * @param time
	 */
	public void setLastFoundDate(Long time)
	{
		lastFoundDate = time;
		setFound(time != null);

	}

	public Long getLastFoundDate()
	{
		return lastFoundDate;
	}
}